from collections import namedtuple
import configparser
import os
import re
import sys

Signal = namedtuple('Signal', ['name', 'width'])

re_signal = re.compile(r'(\w+)(\[(\d+):(\d+)\])?')

def expand_encoding(enc):
    if enc == []:
        return ['']
    item, enc1 = enc[0], enc[1:]
    if type(item) == str:
        return [item + s for s in expand_encoding(enc1)]
    if type(item) == list:
        result = []
        enc1_s = expand_encoding(enc1)
        for choice in item:
            for s in enc1_s:
                result.append(choice + s)
        return result
    assert False

def parse_encoding(s):
    pos = 0
    end = len(s)
    result = []
    while pos < end:
        match = re.match(r'([01x]+)|\(([01x]+(?:\|[01x]+)*)\)', s[pos:])
        pos += len(match.group(0))
        g1, g2 = match.group(1, 2)
        if g1:
            result.append(g1)
        elif g2:
            result.append(g2.split('|'))
        else:
            raise ValueError()
    return expand_encoding(result)

def parse(path):
    config = configparser.ConfigParser()
    config.read(path)
    classes = config.sections()[1:]
    encodings = [parse_encoding(config[cls]['enc']) if 'enc' in config[cls] else None for cls in classes]
    output_signals = []
    expr_table = []
    for k, v in config['OUTPUT'].items():
        fields = v.split()
        if len(fields) == 2:
            v, default = fields
        elif len(fields) == 1:
            default = ''
        else:
            raise ValueError(v)
        output_signals.append(Signal(k, int(v)))
        expr_table_row = [config[cls].get(k, default) for cls in classes]
        expr_table.append(expr_table_row)
    return classes, encodings, output_signals, expr_table

def main():
    (source_fpath,) = sys.argv[1:]
    classes, encodings, output_signals, expr_table = parse(source_fpath)
    class_num = len(classes)
    os.makedirs('gen/bb', exist_ok=True)
    # generate stage 1 decoder
    with open('gen/inst_decoder_stage1.v', 'w') as f:
        f.write(f'''\
module inst_decoder_stage1(i_valid, i_inst, i_irq, i_irq_mask, o_inst_class);
input i_valid;
input [31:0] i_inst;
input i_irq;
input i_irq_mask;
output reg [{class_num}:0] o_inst_class;
''')
        f.write('always @* begin\n')
        shift_lhs = f"{{{class_num}'b0,i_valid}}"
        f.write(f'  if (i_irq & ~i_irq_mask) o_inst_class = {shift_lhs}<<{class_num};\n')
        f.write('  else casez (i_inst)\n')
        for i, bit_patterns in enumerate(encodings):
            if bit_patterns is not None:
                for bit_pattern in bit_patterns:
                    bit_pattern_1 = bit_pattern.replace('x','?')
                    f.write(f"    32'b{bit_pattern_1}: o_inst_class = {shift_lhs}<<{i};\n")
            else:
                f.write(f"    default: o_inst_class = {shift_lhs}<<{i};\n")
        f.write('  endcase\n')
        f.write('end\n')
        f.write('endmodule\n')
    with open('gen/bb/inst_decoder_stage1', 'w') as f:
        f.write('''\
input {
	i_valid;
	i_inst[32];
        i_irq;
        i_irq_mask;
}
output {
	o_inst_class[%d];
}
''' % (class_num+1))
    # generate stage 2 decoder
    with open('gen/inst_decoder_stage2.v', 'w') as f:
        f.write('module inst_decoder_stage2(inst, inst_class, %s);\n' %
                ', '.join(map(lambda signal: signal.name, output_signals)))
        f.write('/* verilator lint_off UNUSED */ input [31:0] inst; /* verilator lint_on UNUSED */\n')
        f.write('input [%d:0] inst_class;\n' % (class_num))
        for signal in output_signals:
            f.write('output reg [%d:0] %s;\n' % (signal.width-1, signal.name))
        f.write('always @* begin\n')
        f.write("  case (1'b1)\n")
        for i, cls in enumerate(classes):
            f.write("    inst_class[%d]: begin\n" % i)
            for signal_index, signal in enumerate(output_signals):
                expr = expr_table[signal_index][i]
                if expr == '':
                    expr = "%d'bx" % signal.width
                f.write("      %s = %s;\n" % (signal.name, expr))
            f.write('    end\n')
        # default case (should not happen)
        f.write('    default: begin\n')
        for s in output_signals:
            f.write("      %s = %d'bx;\n" % (s.name, s.width))
        f.write('    end\n')
        f.write('  endcase\n')
        f.write('end\n')
        f.write('endmodule\n')
    with open('gen/bb/inst_decoder_stage2', 'w') as f:
        f.write('input {\n')
        f.write('\tinst[32];\n')
        f.write('\tinst_class[%d];\n' % (class_num+1))
        f.write('}\n')
        f.write('output {\n')
        for s in output_signals:
            f.write('\t%s[%d];\n' % (s.name, s.width))
        f.write('}\n')
    # generate encoding block for pipegen
    with open('gen/encoding', 'w') as f:
        f.write('encoding {\n')
        for cls in classes:
            f.write(f'\t{cls};\n')
        f.write('\tirq;')
        f.write('}\n')

if __name__ == "__main__":
    main()
